/*
 *  Axolotl - Alternate persistence for Grails
 *  Copyright (C) 2008  Juanjo García Latre
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.googlecode.axolotl.query.groovy;

import groovy.lang.Closure;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.Range;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.codehaus.groovy.runtime.InvokerHelper;

import com.googlecode.axolotl.query.And;
import com.googlecode.axolotl.query.Between;
import com.googlecode.axolotl.query.Conjunction;
import com.googlecode.axolotl.query.Criterion;
import com.googlecode.axolotl.query.Equals;
import com.googlecode.axolotl.query.GreaterOrEqual;
import com.googlecode.axolotl.query.In;
import com.googlecode.axolotl.query.LessOrEqual;
import com.googlecode.axolotl.query.Or;
import com.googlecode.axolotl.query.Order;
import com.googlecode.axolotl.query.PropertyComparison;
import com.googlecode.axolotl.query.PropertyName;
import com.googlecode.axolotl.query.Query;


/**
 * 
 * TODO validate property names using BeanUtils.getPropertyDescriptor()
 * 
 * @author juanjo
 */
public class QueryBuilder extends GroovyObjectSupport {
	
	private Query query;
	private Object currentNode;
	
	
	public QueryBuilder(Class type) {
		this.query = new Query(type);
		this.currentNode = query;
	}
	
	
	public Query getQuery() {
		return query;
	}
	
	
	@Override
	public Object invokeMethod(String name, Object args) {
		List list = InvokerHelper.asList(args);
		
		if (list.size() == 1) {
			if (list.get(0) instanceof Closure) {
				Closure closure = (Closure) list.get(0);
				if ("and".equals(name)) {
					And and = new And();
					attachCriterion(and);
					invokeClosure(closure, and);
					return and;
				}
				else if ("or".equals(name)) {
					Or or = new Or();
					attachCriterion(or);
					invokeClosure(closure, or);
					return or;
				}
				else if ("not".equals(name)) {
					return negate( invokeClosure(closure, currentNode) );
				}
				else if ("orderBy".equals(name)) {
					if (currentNode == query) {
//						buildingOrderBy = true;
//						invokeClosure(closure, currentNode);
//						buildingOrderBy = false;
					}
				}
			}
			else if (list.get(0) instanceof Map) {
				Map attributes = (Map) list.get(0);
				if (attributes.size() == 1) {
					PropertyName propertyName = new PropertyName( String.valueOf(name) );
					Entry entry = (Entry) attributes.entrySet().iterator().next();
					String operator = entry.getKey().toString();
					Object value = entry.getValue();
					Criterion comparison = createComparison(propertyName, operator, value);
					attachCriterion(comparison);
					return comparison;
				}
			}
			else if (list.get(0) instanceof Iterable) {
				if ("orderBy".equals(name)) {
					Iterator it = ((Iterable) list.get(0)).iterator();
					Order root = new Order( String.valueOf(it.next()) );
					Order o = root;
					while (it.hasNext()) {
						Order next = new Order( String.valueOf(it.next()) );
						o.setNext(next);
						o = next;
					}
					attachOrder(root);
					return root;					
				}
			}
		}
		
		return null;
	}


	private void attachCriterion(Criterion criterion) {
		if (currentNode instanceof Query) {
			((Query) currentNode).setFilter(criterion);
		}
		else if (currentNode instanceof Conjunction) {
			((Conjunction) currentNode).add(criterion);
		}
		else {
			System.out.println("Syntax error attaching criterion");
		}
	}

	
	private void attachOrder(Order order) {
		if (currentNode instanceof Query) {
			Query query = (Query) currentNode;
			if (query.getOrder() == null) {
				query.setOrder(order);
			}
			else {
				((Order) query.getOrder()).setNext(order);
			}
		}
		else {
			System.out.println("Syntax error attaching order");
		}
	}
	
	
	private Object invokeClosure(Closure closure, Object nextNode) {
		Object prevNode = currentNode;
		currentNode = nextNode;
		
		closure.setDelegate(this);
		Object node = closure.call();
		
		currentNode = prevNode;
		return node;
	}
	
	
	private Object negate(Object node) {
		if (node instanceof Criterion) {
			return ((Criterion) node).setNot();
		}
		else {
			return node;
		}
	}


    private PropertyComparison createComparison(PropertyName propertyName, 
    		String operator, Object value) {
    	
		if ("eq".equals(operator)) {
			return new Equals(propertyName, value);
		}
		else if ("ge".equals(operator)) {
			return new GreaterOrEqual(propertyName, value);
		}
		else if ("le".equals(operator)) {
			return new LessOrEqual(propertyName, value);
		}
		else if ("in".equals(operator)) {
			if (value instanceof Iterable) {
				return new In(propertyName, (Iterable) value);
			}
		}
		else if ("between".equals(operator)) {
			if (value instanceof Range) {
				Range range = (Range) value;
				return new Between(propertyName, range.getFrom(), range.getTo());
			}
			else if (value instanceof List) {
				List list = (List) value;
				return new Between(propertyName, list.get(0), list.get(1));
			}
		}
    	return null;
    }
    
}
